import { IDBStore } from './store'
import { IDBDataType, IDBStoreBulkReadOptions } from './types'

export class IDBStoreRead {
  idbStore: IDBStore
  constructor(idbStore: IDBStore) {
    this.idbStore = idbStore
  }

  count(): Promise<number> {
    const db = this.idbStore.getDB()
    const storeName = this.idbStore.getStoreName()
    const transaction = db.transaction(storeName, 'readonly')
    const store = transaction.objectStore(storeName)
    const req = store.count()
    return new Promise(function (resolve, reject) {
      req.onsuccess = function (evt) {
        const target = evt.target as IDBRequest
        resolve(target.result)
      }
      req.onerror = function (evt) {
        const target = evt.target as IDBRequest
        reject(target.error)
      }
    })
  }

  /**
   * 根据主键去读取单条数据
   * @param id 主键
   * @returns
   */
  read(id: string | number): Promise<IDBDataType> {
    const db = this.idbStore.getDB()
    const storeName = this.idbStore.getStoreName()
    const transaction = db.transaction(storeName, 'readonly')
    const store = transaction.objectStore(storeName)
    const req = store.get(id)
    return new Promise(function (resolve, reject) {
      req.onsuccess = function (evt) {
        const target = evt.target as IDBRequest
        resolve(target.result)
      }
      req.onerror = function (evt) {
        const target = evt.target as IDBRequest
        reject(target.error)
      }
    })
  }

  /**
   * 根据若干主键查询
   *
   * @param ids 主键列表
   * @returns 查询成功的数据
   */
  readByIds(ids: string[] | number[]): Promise<Array<IDBDataType | null>> {
    const db = this.idbStore.getDB()
    const storeName = this.idbStore.getStoreName()
    const transaction = db.transaction(storeName, 'readonly')
    const store = transaction.objectStore(storeName)
    const result: Array<IDBDataType | null> = []

    // 注意 idb 无法列表获取某几个键, 只能采用多个查询.
    // IDBKeyRange 的 only 只能获取单个值
    // IDBKeyRange 的 bound 是要获取一个范围的值, 且范围上界和范围下界是有序的.
    // 所以不适用于如 msg1 表这种 idClient 的 uuid 键作为主键的模式. 只能采用多个查询
    ids.forEach((id, idx) => {
      const req = store.get(id)
      req.onsuccess = function (evt) {
        const target = evt.target as IDBRequest
        result[idx] = target.result || null
      }
      req.onerror = function () {
        result[idx] = null
      }
    })

    return new Promise(function (resolve, reject) {
      transaction.oncomplete = function () {
        resolve(result)
      }
      transaction.onerror = function (evt) {
        const target = evt.target as IDBRequest
        reject(target.error)
      }
    })
  }

  /**
   * 批量读取. 删除某个索引下的一个范围内的数据
   * @param options 条件
   * @returns 读出来的数据
   */
  bulkRead(options: IDBStoreBulkReadOptions): Promise<IDBDataType[]> {
    const { keyName, value, lower, upper, lowerOpen = false, upperOpen = false, filter } = options
    let range: IDBKeyRange
    if (typeof value !== 'undefined') {
      range = IDBKeyRange.only(value)
    } else if (typeof lower !== 'undefined' && typeof upper !== 'undefined') {
      range = IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen)
    } else if (typeof lower !== 'undefined') {
      range = IDBKeyRange.lowerBound(lower, lowerOpen)
    } else if (typeof upper !== 'undefined') {
      range = IDBKeyRange.upperBound(upper, upperOpen)
    } else {
      throw new Error('delete: no key range')
    }
    const db = this.idbStore.getDB()
    const storeName = this.idbStore.getStoreName()
    const transaction = db.transaction(storeName, 'readonly')
    const store = transaction.objectStore(storeName)
    const result: IDBDataType[] = []

    // 有 key 则打开索引查询范围, 要是没有索引只能打开全量的
    const req = keyName ? store.index(keyName).openCursor(range) : store.openCursor()
    return new Promise(function (resolve, reject) {
      req.onsuccess = function (evt) {
        const target = evt.target as IDBRequest
        try {
          const cursor = target.result
          if (cursor) {
            // 如果设置了过滤器, 还需要判断是否符合条件
            const ifMatchFilter = filter ? filter(cursor.value) : true
            // 设置了枚举值的, 那就需要判断是否符合条件
            // const ifMatchEnum = enums ? enums.includes(cursor.key) : true
            if (ifMatchFilter) {
              result.push(cursor.value)
            }
            cursor.continue()
          } else {
            resolve(result)
          }
        } catch (err) {
          reject(err)
        }
      }
      req.onerror = function (evt) {
        const target = evt.target as IDBRequest
        reject(target.error)
      }
    })
  }

  // 全表读-但是分页输出
  readAllByPage(
    offset = 0,
    limit = 1000
  ): Promise<{
    offset: number
    count: number
    data: IDBDataType[]
    rawError?: Error | null
  }> {
    const db = this.idbStore.getDB()
    const storeName = this.idbStore.getStoreName()
    const transaction = db.transaction(storeName, 'readonly')
    const objectStore = transaction.objectStore(storeName)
    const request = objectStore.openCursor()
    const resultData: IDBDataType[] = []
    let count = 0
    let hasAdvanceStep = offset > 0 // 是否有分页跳转? 跳转一次后移除标记
    return new Promise((resolve, reject) => {
      request.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest).result
        // 如果已经读完了数据集合, 退出
        if (!cursor) return
        if (hasAdvanceStep) {
          // 跳过前面的 offset 条
          cursor.advance(offset)
          hasAdvanceStep = false
        } else {
          offset++
          count++
          resultData.push(cursor.value)
          if (count < limit) {
            cursor.continue()
          } else {
            // 如果一次已经读完了 limit 条数. 打印并退出
            console.log(`readByPage::read limit:${limit}, current offset:${offset}`)
          }
        }
      }

      request.onerror = (event) => {
        const target = event.target as IDBRequest
        const message = target.error?.message
        if (message === 'Failed to read large IndexedDB value') {
          // 污染数据. issue 说超过一条数据 56kb 可能出现, 但是我没有复现, 只能用好未来提供有脏数据的数据库
          console.error(`readByPage::request onerror due to dirty data, current offset:${offset}`)
        } else {
          console.error(`readByPage::request onerror due to other, current offset:${offset}. message:${message}`)
        }
        // 跳过这条数据, offset + 1
        offset = offset + 1
      }

      transaction.oncomplete = () => {
        // 完成事务
        resolve({
          offset: offset,
          count: count,
          data: resultData
        })
      }

      // 备注: 如果主动调用 transaction.abort() 取消, 是先触发 onerror 事件后触发 onabort 事件
      // 错误通常是由 request onerror 发起, 冒泡给 transaction onerror 的, 这样的错误通常不需要处理 onabort 事件.
      // 但是有例外, 如果这个错误和特定 request 无关, 如 Quota exceeded 超出配额, 磁盘 I/O 错误, 那么需要主动留意 onabort 的处理.
      transaction.onabort = function (evt) {
        const target = evt.target as IDBTransaction
        if (target.error instanceof Error) {
          reject(target.error)
        } else {
          reject(new Error('TransactionAborted'))
        }
      }

      transaction.onerror = (event) => {
        console.error(`readByPage::transaction onerror. current offset:${offset}, restarting...`)
        const target = event.target as IDBRequest
        resolve({
          offset: offset,
          count: count,
          data: resultData,
          rawError: target.error
        })
      }
    })
  }

  /**
   * 读取全部数据
   */
  readAll(): Promise<IDBDataType[]> {
    const db = this.idbStore.getDB()
    const storeName = this.idbStore.getStoreName()
    const transaction = db.transaction(storeName, 'readonly')
    const store = transaction.objectStore(storeName)
    // 注意 getAll API 自 chrome 48 后支持, 如果需要支持更低版本，则需要打开游标来操作.
    const request = store.getAll()

    return new Promise((resolve, reject) => {
      request.onsuccess = function (event) {
        const target = event.target as IDBRequest
        resolve(target.result)
      }
      request.onerror = function (event) {
        const target = event.target as IDBRequest
        reject(target.error)
      }
    })
  }
}
